#include <wdm/os_hcd.h>

extern struct os_hcd_port_driver *os_hcd_port_driver;

void os_hcd_port_attached(os_device hub, int port_no)
{
	os_usbhc *hcd = dev_priv(hub);

#if 0
	if (port->state == USB_STATE_DETACHED) {
		port->state = USB_STATE_ATTACHED;
		/* start enumeration process here */
	}
#endif
}

void os_hcd_port_detached(os_device hub, int port_no)
{
	os_usbhc *hcd = dev_priv(hub);

#if 0
	if (port->state != USB_STATE_DETACHED) {
		port->state = USB_STATE_DETACHED;
	}
#endif
}

status_t hcd_port_start_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	status_t status;

	status = udev->port_driver->start(udev);
	if (!NT_SUCCESS(status)) goto end;
end:
	return status;
}

void hcd_port_stop_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	udev->port_driver->stop(udev);
}

status_t hcd_port_init_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	udev->port_driver = os_hcd_port_driver;
	return udev->port_driver->init(udev);
}

void hcd_port_exit_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	/* !port_driver means not initialized */
	if (udev->port_driver) {
		udev->port_driver->exit(udev);
	}
}

void hcd_port_suspend_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	if (udev->port_driver->suspend) {
		udev->port_driver->suspend(udev);
	}
}

void hcd_port_resume_device(os_device dev)
{
	struct os_hcd_dev *udev = dev_priv(dev);
	if (udev->port_driver->resume) {
		udev->port_driver->resume(udev);
	}
}

struct os_driver hcd_port_driver = {
	hcd_port_start_device,	/* start device */
	hcd_port_stop_device,	/* stop device */
	hcd_port_init_device,	/* alloc memory */
	hcd_port_exit_device,	/* free memory */
	hcd_port_suspend_device,
	hcd_port_resume_device,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
};

void os_hcd_stop_port(os_device hub, int port_no)
{
	os_usbhc *hcd = dev_priv(hub);

	if (port_no < NR_HCD_PORTS) {
		if (hcd->inuse[port_no]) {
			hcd->ports[port_no].state = USB_STATE_DETACHED;
			IoDeleteDevice(hcd->ports[port_no].usb_device);
			xchg(&hcd->inuse[port_no], 0);
		}
	}
}

struct wdm_hcd_port_init {
	os_device hub;
	struct os_hcd_port_driver *port_driver;
	uint16_t idVendor;
	uint16_t idProduct;
	uint16_t bcdDevice;
};

static void hcd_port_init_device_later(os_device dev,
				       struct wdm_hcd_port_init *port_init)
{
	os_device hub = port_init->hub;
	os_usbhc *hcd = dev_priv(hub);
	struct os_hcd_dev *udev = dev_priv(dev);
	struct wdm_device *hub_priv = wdm_dev_priv(hub);
	int port_no = hub_priv->minor;

	udev->port_no = port_no;
	udev->hub = hub;
	udev->dev = dev;
	udev->port_driver = port_init->port_driver;
	udev->idVendor = port_init->idVendor;
	udev->idProduct = port_init->idProduct;
	udev->bcdDevice = port_init->bcdDevice;

	/* TODO: generate hardware IDs here */

	hcd->ports[port_no].usb_device = dev;
}

status_t os_hcd_start_port(os_device hub, int port_no,
			   struct os_hcd_port_driver *uport,
			   size_t objsize,
			   uint16_t vid, uint16_t pid, uint16_t sn)
{
	os_usbhc *hcd = dev_priv(hub);
	struct wdm_device *wdm = wdm_dev_priv(hub);
	status_t status;
	struct wdm_hcd_port_init port_init;

	if (cmpxchg(&hcd->inuse[port_no], 0, 1) != 0)
		return STATUS_INVALID_DEVICE_STATE;

	port_init.port_driver = uport;
	port_init.hub = hub;
	port_init.idVendor = vid;
	port_init.idProduct = pid;
	port_init.bcdDevice = sn;

	status = __wdm_dev_add_device(wdm->fdo->DriverObject,
				      NULL,
				      sizeof (struct os_hcd_dev) + objsize,
				      &hcd_port_driver,
				      &GUID_DEVINTERFACE_USB_DEVICE,
				      port_no,
				      0,
				      &GUID_DEVCLASS_USB_PORT,
				      hcd_port_init_device_later,
				      &port_init);

	if (!NT_SUCCESS(status)) {
		xchg(&hcd->inuse[port_no], 0);
		return status;
	}

	hcd->ports[port_no].state = USB_STATE_ATTACHED;
	return status;
}

#if 0
void sth(void)
{
	PDEVICE_OBJECT      pdo;
	PPDO_DEVICE_DATA    pdodata, old_pdodata;
	NTSTATUS            status;
	ULONG               len;
	PLIST_ENTRY         entry;
	unsigned long i;
	
	PAGED_CODE();
	
	if (plugin->addr <= 0)
		return STATUS_INVALID_PARAMETER;
	ExAcquireFastMutex(&fdodata->Mutex);
	for (entry = fdodata->ListOfPDOs.Flink;
	     entry != &fdodata->ListOfPDOs;
	     entry = entry->Flink) {
		pdodata = CONTAINING_RECORD (entry, PDO_DEVICE_DATA, Link);
		if (plugin->addr == pdodata->SerialNo &&
		    pdodata->DevicePnPState != SurpriseRemovePending) {
			ExReleaseFastMutex (&fdodata->Mutex);
			return STATUS_INVALID_PARAMETER;
		}
	}
	ExReleaseFastMutex(&fdodata->Mutex);
	status = IoCreateDeviceSecure(fdodata->Self->DriverObject,
				      sizeof (PDO_DEVICE_DATA),
				      NULL,
				      FILE_DEVICE_BUS_EXTENDER,
				      FILE_AUTOGENERATED_DEVICE_NAME |FILE_DEVICE_SECURE_OPEN,
				      FALSE,
				      &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX,
				      (LPCGUID)&GUID_DEVCLASS_USB_PORT,
				      &pdo);
	if (!NT_SUCCESS (status)) {
		return status;
	}
	pdodata = (PPDO_DEVICE_DATA) pdo->DeviceExtension;
	len = sizeof(HARDWARE_IDS_TPL);
	pdodata->HardwareIDs = ExAllocatePoolWithTag(NonPagedPool, len,
						     BUSENUM_POOL_TAG);
	if (NULL == pdodata->HardwareIDs) {
		IoDeleteDevice(pdo);
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	RtlStringCchPrintfW(pdodata->HardwareIDs, len/sizeof(wchar_t),
			    HARDWARE_IDS_TPL,
			    plugin->vendor, plugin->product,
			    plugin->version,
			    plugin->vendor, plugin->product);
	for (i = 0; i < sizeof (HARDWARE_IDS_TPL); i++) {
		if ('Z' == pdodata->HardwareIDs[i])
			pdodata->HardwareIDs[i] = 0;
	}
	if (plugin->inum > 1)
		len = sizeof(COMPATIBLE_COMPOSITE_IDS_TPL);
	else
		len = sizeof(COMPATIBLE_IDS_TPL);
	pdodata->compatible_ids = ExAllocatePoolWithTag(NonPagedPool, len,
							BUSENUM_POOL_TAG);
	if (NULL == pdodata->compatible_ids) {
		ExFreePool(pdodata->HardwareIDs);
		IoDeleteDevice(pdo);
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	RtlZeroMemory(pdodata->compatible_ids, len);
	pdodata->compatible_ids_len = len;
	RtlStringCchPrintfW(pdodata->compatible_ids, len/sizeof(wchar_t),
			    (plugin->inum>1)?COMPATIBLE_COMPOSITE_IDS_TPL:COMPATIBLE_IDS_TPL,
			    plugin->int0_class, plugin->int0_subclass, plugin->int0_protocol,
			    plugin->int0_class, plugin->int0_subclass,
			    plugin->int0_class);
	for (i = 0; i < len; i++) {
		if ('Z' == pdodata->compatible_ids[i])
			pdodata->compatible_ids[i] = 0;
	}
	old_pdodata = (PPDO_DEVICE_DATA)InterlockedCompareExchangePointer(&(fo->FsContext),
									  pdodata, 0);
	if (old_pdodata) {
		KdPrint(("you can't plugin again"));
		ExFreePool(pdodata->HardwareIDs);
		ExFreePool(pdodata->compatible_ids);
		IoDeleteDevice(pdo);
		return STATUS_INVALID_PARAMETER;
	}
	pdodata->SerialNo = plugin->addr;
	pdodata->fo = fo;
	pdodata->devid = plugin->devid;
	pdodata->speed = plugin->speed;
	bus_init_pdo(pdo, fdodata);
	
	/* Device Relation changes if a new pdo is created. So let the PNP
	 * system now about that. This forces it to send bunch of pnp
	 * queries and cause the function driver to be loaded.
	 */
	IoInvalidateDeviceRelations(fdodata->UnderlyingPDO, BusRelations);
	return status;
}
#endif

void os_hcd_init_ports(os_usbhc *usb)
{
	int i;
	for (i = 0; i < NR_HCD_PORTS; i++) {
		xchg(&usb->inuse[i], 0);
	}
}

void os_hcd_exit_ports(os_usbhc *usb)
{
	int i;
	for (i = 0; i < NR_HCD_PORTS; i++) {
		BUG_ON(usb->inuse[i] != 0);
	}
}
